#!/usr/bin/python
#-*-coding: utf-8 -*-

#This file is part of pyLot library.
#
# pyLot is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyLot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pyLot.  If not, see <http://www.gnu.org/licenses/>.

__author__ = u'Pierre Puiseux, Guillaume Baty'
__copyright__ = u"Copyright 2011-2012 (C) andheo, Université de Pau et des Pays de l'Adour"
__credits__ = [u'Pierre Puiseux', u'Guillaume Baty']
__license__ = "GNU Lesser General Public License"

__all__ = [u'Collection']

import sys
import warnings

from _pylot.minimal.datum import Datum
from _pylot.legacy.identity import Identity
from _pylot.minimal.path import Path
from _pylot.minimal.pathbook import PathBook
from _pylot.minimal.utils import unicode2, check_type, error, readfile, writefile
from _pylot.minimal.system import ENCODING
from _pylot.data import regexpr

from .book import Book, GroupBook

class Collection (dict) :
  u"""
  An improved dict for handling Books
  """
  best_file_format = u'csv'

  def __init__ (self, *t, **d):
    u"""
    >>> c = Collection(Book(), Book(), name=u'MyCollection')
    >>> print c.name
    MyCollection

    .. seealso::
      doc/specs/

    """
    if u'book_type' in d :
      self.book_type = d.pop(u'book_type')
    else :
      self.book_type = Book

    #Les cles de Datum sont considérées comme faisant partie de Identity
    datum_dict = {}
    for key in Datum.ordered_keys :
      if key in d :
        datum_dict[key] = d.pop(key)
    #on initialise Identity

    # TODO: Python3: remove lines from here ...
    new_dict = {}
    for key, value in datum_dict.items() :
      new_dict[key.encode(ENCODING)] = value
    # ... to here

    self.identity = Identity()
    self.identity.upFromDict(new_dict)
    dict.__init__(self)

    #on met a jour la collection elle meme
    self._upCollection(*t, **d)


  def _setname(self, name) :
    self.identity.name = name

  def _getname(self):
    return self.identity.name

  def _getalias(self):
    return self.identity.alias

  def _gettooltip(self):
    return self.identity.tooltip

  def _settooltip(self, tooltip):
    self.identity.tooltip = tooltip

  tooltip = property(fget=_gettooltip, fset=_settooltip)
  name = property(fget=_getname, fset=_setname,
                  doc=u"collection name, stored in identity")
  alias = property(fget=_getalias, doc=u"collection alias, stored in identity")

  def __setitem__ (self, key, book):
    check_type(u'book', book, (Book, PathBook)) # Now PathBook inherits from Container !
    if key != book.name :
      warnings.warn(u'Since 2009/04/28: book name changed to key value instead of raising exception', DeprecationWarning)
      book.name = key
    dict.__setitem__(self, key, book)

  def __call__(self, block, var=u''):
    if not var : return self[block]
    else : return unicode2(self[block](var))

  def setValue(self, book_id, datum_id, value):
    u"""
    >>> book1 = Book(Datum(u'i', int, 1), title=u'Livre1')
    >>> c = Collection(book1, name=u'collec')
    >>> c.setValue(u'Livre1', u'i', 3)
    >>> print c[u'Livre1'][u'i']
    Datum(u'i', int, 3)
    >>> c.setValue(u'AutreLivre', u'i', 3)
    Traceback (most recent call last):
    ...
    KeyError: "Book 'AutreLivre' not in Collection 'collec'"
    >>> c.setValue(u'Livre1', u'autre_datum', 3)
    Traceback (most recent call last):
    ...
    KeyError: "Datum 'autre_datum' not in Book 'Livre1' (Collection: 'collec')"
    """
    if book_id not in self :
      raise KeyError, error(tr_(u"Book '%s' not in Collection '%s'") % (
        book_id, self.name))
    if datum_id not in self[book_id] :
      raise KeyError, error(tr_(u"Datum '%s' not in Book '%s' (Collection: '%s')") % (
        datum_id, book_id, self.name))
    self[book_id][datum_id].value = value

  def hasDatum(self, book_id, datum_id):
    if book_id not in self :
      return False
    else :
      return datum_id in self[book_id]

  def addBook(self, book=None) :
    if book is None :
      book = self.book_type()
    assert(isinstance(book, (Book, self.book_type)))
    if not book.name in self :
      self[book.name] = book

  def upFromBook(self, book, mod_attrs=None, only_existing_keys=False):
    check_type(u'book', book, Book)
    check_type(u'mod_attrs', mod_attrs, [list, tuple], none=True)
    check_type(u'only_existing_keys', only_existing_keys, bool)

    if mod_attrs is None :
      mod_attrs = book.datum_type.getFieldsAll()
    if book.title in self :
      self[book.title].upgrade(book, mod_attrs=mod_attrs, strict=False,
                               only_existing_keys=only_existing_keys)
    elif only_existing_keys is False :
      self[book.title] = book
    else :
      # only existing keys are updated, so, nothing is done
      pass

  def _upCollection(self, *t, **d):
    if u'mod_attrs' in d :
      mod_attrs = d.pop(u'mod_attrs')
    else :
      mod_attrs = Datum.getFieldsAll()
    if u'only_existing_keys' in d :
      only_existing_keys = d.pop(u'only_existing_keys')
    else :
      only_existing_keys = False
    if len(t) == 0: #tous les arg sont nommés
      self.upgrade(d, only_existing_keys=only_existing_keys)
    else:
      for elem in t :
        if isinstance(elem, Book) :
          #t = [Book, Book, ...]
          self.upgrade({elem.name:elem}, mod_attrs=mod_attrs,
                       only_existing_keys=only_existing_keys)
        elif isinstance(elem, Collection) :
          self.upgrade(elem, mod_attrs=mod_attrs,
                       only_existing_keys=only_existing_keys)

  def upgrade (self, collection, mod_attrs=Datum.getFieldsAll(),
               only_existing_keys=False):
    for value in collection.values() :
      assert (isinstance(value, (self.book_type, Book)))
      self.upFromBook(value, mod_attrs=mod_attrs,
                      only_existing_keys=only_existing_keys)

  def resetDataToDefault(self):
    u"""
    Sets every data to default_value
    """
    for book in self.values() :
      book.resetDataToDefault()

  def _upFromBlockStructured (self, stream, entries, mod_attrs, up_from_line,
                              block_regexpr, only_existing_keys):
    books = self.__class__()
    # TODO: utiliser line.lstrip('@@Collection') au lieu de 
    # ' '.join(line.split()[1:])
    for line in stream :
      line = line.strip()
      if line :
        if regexpr.co_comment.match(line) : continue
        elif regexpr.co_file_collection_name.match(line) :
          self.identity.clear()
          self.identity.upFromCsv(u' '.join(line.split()[1:]))
          continue
        elif block_regexpr.match(line) :
          identity = Identity()
          identity.upFromCsv(u' '.join(line.split()[1:]))
          bookname = identity.name
          if identity.nature == Path and issubclass(self.book_type, Book):
            books[bookname] = Book(identity=identity)
          else :
            books[bookname] = self.book_type(identity=identity)
          continue
        elif regexpr.co_file_collection_groupbook.match(line) :
          identity = Identity()
          identity.upFromCsv(u' '.join(line.split()[1:]))
          bookname = identity.name
          books[bookname] = GroupBook(identity=identity)
        else :
          datum = up_from_line(line)
          books[bookname].upFromDatum(datum, mod_attrs=mod_attrs)
          continue
    self._upCollection(books, mod_attrs=mod_attrs,
                       only_existing_keys=only_existing_keys)

  def readCsv(self, stream) :
    u"""
    """
    self._upFromBlockStructured(stream, None,
      Datum.getFieldsAll(), self.book_type.datum_type.FromCsv,
      regexpr.co_file_collection_book, False)

  def upFromFile (self, src, dataformat=None, entries=None,
                  mod_attrs=Datum.getFieldsAll(), only_existing_keys=False) :
    u"""
    """
    src = Path(src)
    if dataformat is None :
      if src.ext == u'.csv' : dataformat = u'csv'
      elif src.ext == u'.pylot' : dataformat = u'pylot'
      else : raise ValueError, error(tr_(u'Unknown file type %s') % src)
    else :
      dataformat = dataformat.lower()

    assert(dataformat in (u'csv', u'pylot'))
    try :
      stream = readfile(src)
    except (OSError, IOError) :
      pass
    else :
      if dataformat == u'csv' :
        self._upFromBlockStructured(stream, entries,
          mod_attrs, self.book_type.datum_type.FromCsv,
          regexpr.co_file_collection_book, only_existing_keys)
      elif dataformat == u'pylot' :
        self._upFromBlockStructured(stream, entries,
          (u'value', u'value_list'), self.book_type.datum_type.FromPyLot,
          regexpr.co_pylot_file_block, only_existing_keys)

      stream.close()

  def orderKeys(self, alpha_sort):
    keys = self.keys()
    if alpha_sort is True :
      keys.sort()
    return keys

  def toStream (self, stream=sys.stdout, fmt=best_file_format,
                indent=u'  ', alpha_sort=False):
    if fmt.lower() == u'csv' :
      stream.write(u"\n@@Collection %s \n" % self.identity.toCsv())
      for blockid in self.orderKeys(alpha_sort) :
        stream.write(u"\n@Book %s \n" % self[blockid].identity.toCsv())
        self[blockid].toStream(stream, fmt=u'csv', indent=indent,
                               alpha_sort=alpha_sort)

    elif fmt.lower() == u'pylot' :
      for blockid in self.orderKeys(alpha_sort) :
        stream.write(u"\n//Block %s\n" % blockid)
        self[blockid].toStream(stream, fmt=u'pylot', indent=indent,
                               alpha_sort=alpha_sort)

    else :
      raise ValueError, error(tr_(u'Unknown file format %s') % fmt)

  def toFile (self, dest, dataformat, indent=u'  ', alpha_sort=False):
    warnings.warn(u'Since 2009-03-30: You must use exportfile method instead of toFile',
                  DeprecationWarning)
    return self.exportfile(dest, fmt=dataformat)

  def exportfile (self, dest, fmt=u'csv'):
    fout = writefile(dest, security=u'force')
    if fmt.lower() == u'csv' :
      fout.write(u'# Collection CSV file -- version : 0.1\n')
    elif fmt.lower() == u'pylot' :
      fout.write(u'# pylotparam file -- version : 0.1\n')
    self.toStream(fout, fmt, indent=u'  ', alpha_sort=True)
    fout.close()

  def importfile (self, src, fmt=u'csv'):
    self.clear()
    return self.upFromFile (src, dataformat=fmt)

  def __repr__(self):
    u"""
    Collection representation
    """
    ustr = u''
    for bloc, book in self.items():
      ustr += (u"\n//Block %s" % bloc)
      for var in book.values() :
        ustr += (u"\n  %s %s %s" % (var.name, var.value, var.value_list))
    return ustr

  def __unicode__(self):
    if self.name:
      ustr = self.name + u'.'
    else:
      ustr = u'Not a named collection.'
    if self.alias :
      ustr += u' Alias=%s.' % self.alias
    if self.tooltip :
      ustr += u' Tooltip=%s.' % self.tooltip
    ustr += u' Contains Books : '
    for book in self.values() :
      ustr += u'\n   => %s (%d entries)' % (book.name, len(book.keys()))
    return ustr

  def __str__ (self):
    return unicode(self).encode(ENCODING)

  def __eq__ (self, other):
    assert(type(self) == type(other))
    if not (self.identity == other.identity) :
      return False
    for book in self :
      try:
        if self[book] != other[book] :
          return False
      except KeyError :
        return False
    return True

  def __ne__ (self, other):
    return not self.__eq__(other)

if __name__ == u'__main__' :
  import doctest
  doctest.testmod()

